/**
 * MIT License
 *
 * Copyright (c) 2024 小村程序员
 * Email: 397237738@qq.com
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.open.cobertura.core.instrument;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.AnalyzerAdapter;
import org.objectweb.asm.tree.MethodNode;
import org.open.cobertura.core.provider.CoberturaProbeInsertProvider;
import org.open.cobertura.core.provider.ICouerturaProbeInsertProvider;
import org.open.cobertura.core.provider.CoberturaProbeIndexProvider;
import org.open.cobertura.core.util.CoberturaLabelStatefulUtil;

public class CoberturaMethodProbeAdapter  extends MethodVisitor {
    private CoberturaMethodProbeVisitor mv;
    private CoberturaProbeIndexProvider probeIndexProvider;
    private long classId;
    private String className;
    private String methodName;
    private CoberturaLabelStatefulVisitor flowerInfo;
    private Label label;
    private int lineNumber;
    private ICouerturaProbeInsertProvider probeInsertProvider;
    private AnalyzerAdapter analyzerAdapter;
    MethodNode mvn;
    protected CoberturaMethodProbeAdapter(final int api,final CoberturaMethodProbeVisitor methodVisitor,final String methodName,final long classId,final String className,final CoberturaProbeIndexProvider probeIndexProvider) {
        super(api, methodVisitor);
        this.mv=methodVisitor;
        this.classId=classId;
        this.className=className;
        this.methodName=methodName;
        this.probeIndexProvider=probeIndexProvider;
        this.probeInsertProvider=new CoberturaProbeInsertProvider();

    }

    @Override
    public void visitIntInsn(int opcode, int operand) {
        super.visitIntInsn(opcode, operand);
    }

    @Override
    public void visitInsn(final int opcode) {

        switch (opcode) {
            case Opcodes.IRETURN:
                mv.insertProbe(Opcodes.IRETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.LRETURN:
                mv.insertProbe(Opcodes.LRETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.FRETURN:
                mv.insertProbe(Opcodes.FRETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.DRETURN:
                mv.insertProbe(Opcodes.DRETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.ARETURN:
                mv.insertProbe(Opcodes.ARETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.RETURN:
                mv.insertProbe(Opcodes.RETURN, probeIndexProvider.indexIncrement());
                break;
            case Opcodes.ATHROW:
                mv.insertProbe(Opcodes.ATHROW, probeIndexProvider.indexIncrement());
                break;
            default:
                mv.visitInsn(opcode);
                break;
        }
    }

    @Override
    public void visitVarInsn(int opcode, int varIndex) {
        mv.visitVarInsn(opcode, varIndex);

    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
        mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }

    @Override
    public void visitJumpInsn(int opcode, Label label) {
        if(CoberturaLabelStatefulUtil.isLabelStateForIsMultiJump(label)){
//            IFrame frame= frame(jumpPopCount(opcode));
            IFrame frame= null;
            int index=probeIndexProvider.indexIncrement();
            mv.insertJumpProbe(opcode,index,label,frame);
        }else{
            mv.visitJumpInsn(opcode, label);
        }
    }

    @Override
    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
        super.visitTableSwitchInsn(min, max, dflt, labels);
    }

    @Override
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        super.visitLookupSwitchInsn(dflt, keys, labels);
    }

    @Override
    public void visitLabel(Label label) {
        if(CoberturaLabelStatefulUtil.isNeedProbe(label)){
            int index=probeIndexProvider.indexIncrement();
            mv.insertProbe(index);
        }
        mv.visitLabel(label);
    }

    @Override
    public void visitLineNumber(int line, Label start) {
        mv.visitLineNumber(line, start);
        this.lineNumber=line;
    }

    @Override
    public void visitCode() {
        mv.visitCode();

    }

    @Override
    public void visitEnd() {
        mv.visitEnd();
    }



    public void setAnalyzerAdapter(AnalyzerAdapter analyzerAdapter) {
        this.analyzerAdapter = analyzerAdapter;
    }

    private IFrame frame(final int popCount) {
        return FrameSnapshot.create(analyzerAdapter, popCount);
    }
    private int jumpPopCount(final int opcode) {
        switch (opcode) {
            case Opcodes.GOTO:
                return 0;
            case Opcodes.IFEQ:
            case Opcodes.IFNE:
            case Opcodes.IFLT:
            case Opcodes.IFGE:
            case Opcodes.IFGT:
            case Opcodes.IFLE:
            case Opcodes.IFNULL:
            case Opcodes.IFNONNULL:
                return 1;
            default: // IF_CMPxx and IF_ACMPxx
                return 2;
        }

    }
}
